package com.mongo.util;

import com.mongo.annotation.EncryptField;
import com.mongo.enums.EncryptType;
import com.mongo.secret.*;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.env.Environment;
import org.springframework.util.Assert;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

/**
 * 加解密工厂类
 * @author xiazhun
 * @create 2022/9/20
 */
public class SecretFactory {
    public static final String DES_SECRET = "desEncrypt";
    public static final String AES_SECRET = "aesEncrypt";

    public static Secret type(EncryptType encryptType) {
        Secret secret = null;
        switch (encryptType) {
            case DES:
                secret = Des.getInstance();
                break;
            case SM2:
                secret = Sm2.getInstance();
                break;
            case AES:
                secret = Aes.getInstance();
                break;
            case RSA:
                secret = Rsa.getInstance();
                break;
            case NONE:
                break;
            default:
        }
        return secret;
    }

    public static <T> T decode(boolean flag, T t) {
        if (!flag) {
            return t;
        }
        Field[] filedName = getFiledName(t);
        if (null == filedName || ArrayUtils.isEmpty(filedName)) {
            return t;
        }
        for (Field field : filedName) {
            if (null != field) {
                EncryptField annotation = field.getAnnotation(EncryptField.class);
                boolean b = annotation.returnDecode();
                //获取加解密工具
                Secret secret = SecretFactory.type(annotation.type());
                if (null != secret) {
                    String privateKey = null;
                    Object value = ReflectionUtil.getValue(t, field);
                    EncryptType type = annotation.type();
                    if (Arrays.asList(EncryptType.SM2, EncryptType.RSA).contains(type)) {
                        Environment environment = SpringUtils.getBean(Environment.class);
                        //获取全局公钥
                        String property = environment.getProperty("mongo.secret.privateKey.path");
                        if (StringUtils.isNotBlank(property)){
                            String path = Des.getInstance().decode(property);
                            File file = new File(path);
                            try {
                                FileReader reader = new FileReader(file);
                                BufferedReader br = new BufferedReader(reader);
                                StringBuilder sb = new StringBuilder();
                                String line;
                                while((line = br.readLine()) != null){
                                    sb.append(line);
                                }
                                privateKey = sb.toString();
                            }catch (Exception e){
                                throw new RuntimeException("找不到私钥文件");
                            }

                        }else{
                            privateKey = annotation.privateKey();
                        }
                        Assert.notNull(privateKey, "privateKey must not be null");
                    } else {
                        String keys = annotation.key();
                        if (StringUtils.isNotBlank(keys)) {
                            //密钥
                            privateKey = keys;
                        }
                    }
                    if (null != value) {
                        if (value instanceof String) {
                            if (b) {
                                String decode = value.toString();
                                try {
                                     decode = secret.decode(value.toString(), privateKey);
                                     //解密失败返回原值
                                }catch (Exception ignore){}
                                ReflectionUtil.setValue(t, field, decode);
                            }

                        }
                        if (value instanceof Iterable) {
                            Iterator<?> iterator = ((Collection<?>) value).iterator();
                            while (iterator.hasNext()) {
                                try {
                                    decode(true, iterator.next());
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        } else {
                            decode(true, value);
                        }
                    }
                }

            }
        }
        return t;
    }


    public static <T> T encrypt(boolean flag, T t) {
        if (!flag) {
            return t;
        }
        Field[] filedName = getFiledName(t);
        if (null == filedName || ArrayUtils.isEmpty(filedName)) {
            return t;
        }
        for (Field field : filedName) {
            if (null != field) {
                EncryptField annotation = field.getAnnotation(EncryptField.class);
                //获取加解密工具
                Secret secret = SecretFactory.type(annotation.type());
                if (null != secret) {
                    String publicKey = null;
                    EncryptType type = annotation.type();
                    if (Arrays.asList(EncryptType.SM2, EncryptType.RSA).contains(type)) {
                        Environment environment = SpringUtils.getBean(Environment.class);
                        //获取全局私钥
                        String property = environment.getProperty("mongo.secret.publicKey.path");
                        if (StringUtils.isNotBlank(property)) {
                            String path = Des.getInstance().decode(property);
                            File file = new File(path);
                            try {
                                FileReader reader = new FileReader(file);
                                BufferedReader br = new BufferedReader(reader);
                                StringBuilder sb = new StringBuilder();
                                String line;
                                while ((line = br.readLine()) != null) {
                                    sb.append(line);
                                }
                                publicKey = sb.toString();
                            } catch (Exception e) {
                                throw new RuntimeException("找不到公钥文件");
                            }
                        }else{
                            publicKey = annotation.publicKey();
                        }
                        Assert.notNull(publicKey, "privateKey must not be null");
                    }else {
                        String keys = annotation.key();
                        if (StringUtils.isNotBlank(keys)) {
                            //密钥
                            publicKey = keys;
                        }
                    }
                    Object value = ReflectionUtil.getValue(t, field);
                    if (null != value) {
                        if (value instanceof String) {
                            String decode = secret.encrypt(value.toString(), publicKey);
                            ReflectionUtil.setValue(t, field, decode);
                        }
                        if (value instanceof Iterable) {
                            Iterator<?> iterator = ((Collection<?>) value).iterator();
                            while (iterator.hasNext()) {
                                try {
                                    encrypt(true, iterator.next());
                                } catch (Exception e) {
                                    e.printStackTrace();
                                }
                            }
                        } else {
                            encrypt(true, value);
                        }
                    }
                }

            }
        }
        return t;
    }

    public static Field[] getFiledName(Object t) {
        if (t == null) {
            return null;
        }
        Field[] fields = t.getClass().getDeclaredFields();
        Field[] fieldNames = new Field[fields.length];
        for (int i = 0; i < fields.length; i++) {
            boolean fieldHasAnno = fields[i].isAnnotationPresent(EncryptField.class);
            if (fieldHasAnno) {
                fieldNames[i] = fields[i];
            }
        }
        return fieldNames;
    }

    public static Field[] getFiledName(Class<?> clazz) {
        Field[] fields = clazz.getDeclaredFields();
        Field[] fieldNames = new Field[fields.length];
        for (int i = 0; i < fields.length; i++) {
            boolean fieldHasAnno = fields[i].isAnnotationPresent(EncryptField.class);
            if (fieldHasAnno) {
                fieldNames[i] = fields[i];
            }
        }
        return fieldNames;
    }

}
